/*-------------------------------------------------------------------------
 *
 * miscinit.c
 *	  杂项初始化支持
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/init/miscinit.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/param.h>
#include <signal.h>
#include <time.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <utime.h>

#include "access/htup_details.h"
#include "access/parallel.h"
#include "catalog/pg_authid.h"
#include "common/file_perm.h"
#include "libpq/libpq.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "postmaster/postmaster.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/pidfile.h"
#include "utils/syscache.h"
#include "utils/varlena.h"


#define DIRECTORY_LOCK_FILE		"postmaster.pid"

ProcessingMode Mode = InitProcessing;

BackendType MyBackendType;

/* 要在进程退出时移除的锁文件列表 */
static List *lock_files = NIL;

static Latch LocalLatchData;

/* ----------------------------------------------------------------
 *		忽略系统索引支持内容
 *
 * 注意：“忽略系统索引”意味着我们不会使用系统索引
 * 来进行查找（无论是在硬编码的目录访问还是在计划生成的
 * 计划中）。然而，我们仍然会在进行目录修改时更新索引。
 * ----------------------------------------------------------------
 */

bool		IgnoreSystemIndexes = false;


/* ----------------------------------------------------------------
 *		通用进程启动代码
 * ----------------------------------------------------------------
 */

/*
 * 为postmaster子进程初始化基本环境
 *
 * 应在子进程启动后尽早调用。然而，在EXEC_BACKEND构建中，它
 * 需要在read_backend_variables()之后调用。
 */
void InitPostmasterChild(void)
{
	IsUnderPostmaster = true;	/* 我们现在是一个postmaster子进程 */

	/*
	 * 启动我们的win32信号实现。这必须在我们
	 * 读取后端变量之后执行，因为我们需要从
	 * 父进程获取信号管道。
	 */
#ifdef WIN32
	pgwin32_signal_initialize();
#endif

	/*
	 * 设置堆栈深度检查的参考点。这在！EXEC_BACKEND构建中可能看起来
	 * 是多余的，但并非如此，因为postmaster
	 * 是从信号处理程序启动其子进程的，因此我们可能在
	 * 替代堆栈上运行。
	 */
	(void) set_stack_base();

	InitProcessGlobals();

	/*
	 * 确保在任何可能写入stderr之前将其置于二进制模式，
	 * 以防它实际上是syslogger管道，以免打乱管道
	 * 分块协议。非日志管道数据在重定向时
	 * 也会被翻译（例如，通过pg_ctl -l）。
	 */
#ifdef WIN32
	_setmode(fileno(stderr), _O_BINARY);
#endif

	/* 我们不想要postmaster的proc_exit()处理程序 */
	on_exit_reset();

	/* 在EXEC_BACKEND情况下，我们将没有继承的BlockSig等值 */
#ifdef EXEC_BACKEND
	pqinitmask();
#endif

	/* 初始化进程本地闩锁支持 */
	InitializeLatchSupport();
	MyLatch = &LocalLatchData;
	InitLatch(MyLatch);
	InitializeLatchWaitSet();

	/*
	 * 如果可能的话，使此进程成为组领导者，以便postmaster
	 * 也可以向任何子进程发出信号。并非所有进程都有
	 * 子进程，但为了保持一致性，我们使所有postmaster子进程
	 * 都执行此操作。
	 */
#ifdef HAVE_SETSID
	if (setsid() < 0)
		elog(FATAL, "setsid() failed: %m");
#endif

	/*
	 * 每个postmaster子进程预计始终会迅速响应
	 * SIGQUIT。因此，我们集中移除BlockSig中的SIGQUIT并安装一个
	 * 合适的信号处理程序。（面向客户的进程可以选择用
	 * quickdie()替换此默认处理程序的选择。）其他可阻塞信号
	 * 目前仍保持被阻塞状态。
	 */
	pqsignal(SIGQUIT, SignalHandlerForCrashExit);

	sigdelset(&BlockSig, SIGQUIT);
	PG_SETMASK(&BlockSig);

	/* 如果可能，请求一个信号，如果postmaster崩溃。 */
	PostmasterDeathSignalInit();
}

/*
 * 为独立进程初始化基本环境。
 *
 * argv0必须能够找到程序的可执行文件。
 */
void InitStandaloneProcess(const char *fc_argv0)
{
	Assert(!IsPostmasterEnvironment);

	/*
	 * 启动我们的win32信号实现
	 */
#ifdef WIN32
	pgwin32_signal_initialize();
#endif

	InitProcessGlobals();

	/* 初始化进程本地闩锁支持 */
	InitializeLatchSupport();
	MyLatch = &LocalLatchData;
	InitLatch(MyLatch);
	InitializeLatchWaitSet();

	/*
	 * 为了与InitPostmasterChild的一致性，在此初始化信号掩码。
	 * 但我们不会解锁SIGQUIT或为其提供默认处理程序。
	 */
	pqinitmask();
	PG_SETMASK(&BlockSig);

	/* 计算路径，没有postmaster可继承 */
	if (my_exec_path[0] == '\0')
	{
		if (find_my_exec(fc_argv0, my_exec_path) < 0)
			elog(FATAL, "%s: could not locate my own executable path",
				 fc_argv0);
	}

	if (pkglib_path[0] == '\0')
		get_pkglib_path(my_exec_path, pkglib_path);
}

void SwitchToSharedLatch(void)
{
	Assert(MyLatch == &LocalLatchData);
	Assert(MyProc != NULL);

	MyLatch = &MyProc->procLatch;

	if (FeBeWaitSet)
		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
						MyLatch);

	/*
	 * 将共享锁设置为本地锁，尽管本地锁可能已经被设置。通常情况下，这并不是必要的，因为代码应该在等待锁之前检查条件，但多一份小心总是好的。
	 */
	SetLatch(MyLatch);
}

void SwitchBackToLocalLatch(void)
{
	Assert(MyLatch != &LocalLatchData);
	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);

	MyLatch = &LocalLatchData;

	if (FeBeWaitSet)
		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
						MyLatch);

	SetLatch(MyLatch);
}

const char * GetBackendTypeDesc(BackendType fc_backendType)
{
	const char *fc_backendDesc = "unknown process type";

	switch (fc_backendType)
	{
		case B_INVALID:
			fc_backendDesc = "not initialized";
			break;
		case B_AUTOVAC_LAUNCHER:
			fc_backendDesc = "autovacuum launcher";
			break;
		case B_AUTOVAC_WORKER:
			fc_backendDesc = "autovacuum worker";
			break;
		case B_BACKEND:
			fc_backendDesc = "client backend";
			break;
		case B_BG_WORKER:
			fc_backendDesc = "background worker";
			break;
		case B_BG_WRITER:
			fc_backendDesc = "background writer";
			break;
		case B_CHECKPOINTER:
			fc_backendDesc = "checkpointer";
			break;
		case B_STARTUP:
			fc_backendDesc = "startup";
			break;
		case B_WAL_RECEIVER:
			fc_backendDesc = "walreceiver";
			break;
		case B_WAL_SENDER:
			fc_backendDesc = "walsender";
			break;
		case B_WAL_WRITER:
			fc_backendDesc = "walwriter";
			break;
		case B_ARCHIVER:
			fc_backendDesc = "archiver";
			break;
		case B_LOGGER:
			fc_backendDesc = "logger";
			break;
	}

	return fc_backendDesc;
}

/* ----------------------------------------------------------------
 *				数据库路径/名称支持相关内容
 * ----------------------------------------------------------------
 */

void SetDatabasePath(const char *fc_path)
{
	/* 每个进程只应发生一次 */
	Assert(!DatabasePath);
	DatabasePath = MemoryContextStrdup(TopMemoryContext, fc_path);
}

/*
 * 验证提议的数据目录。
 *
 * 还要初始化文件和目录的创建模式及模式掩码。
 */
void checkDataDir(void)
{
	struct stat fc_stat_buf;

	Assert(DataDir);

	if (stat(DataDir, &fc_stat_buf) != 0)
	{
		if (errno == ENOENT)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("data directory \"%s\" does not exist",
							DataDir)));
		else
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not read permissions of directory \"%s\": %m",
							DataDir)));
	}

	/* 最终的 chdir 无论如何都会失败，但我们来测试一下... */
	if (!S_ISDIR(fc_stat_buf.st_mode))
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("specified data directory \"%s\" is not a directory",
						DataDir)));

	/*
	 * 检查目录是否属于我的用户ID；如果不是，则拒绝。
	 *
	 * 此检查是阻止两个邮政主管在同一目录下启动的重要部分（见 CreateLockFile()）。
	 * 请勿删除或削弱它。
	 *
	 * XXX 我们可以在 Windows 上安全地启用此检查吗？
	 */
#if !defined(WIN32) && !defined(__CYGWIN__)
	if (fc_stat_buf.st_uid != geteuid())
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("data directory \"%s\" has wrong ownership",
						DataDir),
				 errhint("The server must be started by the user that owns the data directory.")));
#endif

	/*
	 * 检查目录是否具有正确的权限。如果没有，则拒绝。
	 *
	 * 仅允许两种可能的模式，0700 和 0750。后者模式
	 * 表示所有新创建的文件和目录应允许组读取/执行。
	 *
	 * XXX 在 Windows 上暂时抑制检查，因为可能不具备对类 Unix 文件权限的
	 * 适当支持。需要考虑在 Windows 上应用合理检查。
	 */
#if !defined(WIN32) && !defined(__CYGWIN__)
	if (fc_stat_buf.st_mode & PG_MODE_MASK_GROUP)
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("data directory \"%s\" has invalid permissions",
						DataDir),
				 errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
#endif

	/*
	 * 根据数据目录的模式重置创建模式和掩码。
	 *
	 * 在启动时，掩码被设置为禁止新创建的文件和目录的组权限。
	 * 但是，如果数据目录具有组读取/执行权限，则修改创建模式和掩码
	 * 以允许新创建的文件和目录的组读取/执行，并设置 data_directory_mode GUC。
	 *
	 * 在 Windows 上抑制此操作，因为可能不具备对类 Unix 文件权限的适当支持。
	 */
#if !defined(WIN32) && !defined(__CYGWIN__)
	SetDataDirectoryCreatePerm(fc_stat_buf.st_mode);

	umask(pg_mode_mask);
	data_directory_mode = pg_dir_create_mode;
#endif

	/* 检查 PG_VERSION */
	ValidatePgVersion(DataDir);
}

/*
 * 设置数据目录，但确保它是绝对路径。使用此方法，
 * 不要直接设置 DataDir。
 */
void SetDataDir(const char *fc_dir)
{
	char	   *fc_new;

	AssertArg(fc_dir);

	/* 如果给定路径是相对路径，则转换为绝对路径 */
	fc_new = make_absolute_path(fc_dir);

	if (DataDir)
		free(DataDir);
	DataDir = fc_new;
}

/*
 * 更改工作目录为 DataDir。大多数邮政主管和后端
 * 代码假设我们在 DataDir 中，因此可以使用相对路径访问
 * 数据目录中的内容。然而，为了在路径
 * 设置期间的方便，我们不强制在 SetDataDir 中进行 chdir。
 */
void ChangeToDataDir(void)
{
	AssertState(DataDir);

	if (chdir(DataDir) < 0)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not change directory to \"%s\": %m",
						DataDir)));
}


/* ----------------------------------------------------------------
 *	用户 ID 状态
 *
 * 我们必须跟踪与“用户 ID”概念相关的几个不同值。
 *
 * AuthenticatedUserId 在连接开始时确定，并且永不改变。
 *
 * SessionUserId 最初与 AuthenticatedUserId 相同，但可以通过 SET SESSION AUTHORIZATION 更改（如果 AuthenticatedUserIsSuperuser）。这是通过 SESSION_USER SQL 函数报告的 ID。
 *
 * OuterUserId 是当前在“外部级别”（任何事务或函数之外）生效的用户 ID。最初，这与 SessionUserId 相同，但可以通过 SET ROLE 更改为 SessionUserId 是其成员的任何角色。（XXX 重命名为类似 CurrentRoleId 的名称？）
 *
 * CurrentUserId 是当前有效的用户 ID；这是用于所有正常权限检查目的的 ID。在外部级别，这将与 OuterUserId 相同，但在调用 SECURITY DEFINER 函数时会改变，以及在某些专门的命令中本地改变。
 *
 * SecurityRestrictionContext 保存指示改变 CurrentUserId 原因的标志。在某些情况下，我们需要锁定不直接由权限设置控制的操作，这提供了一种方便的方式来做到这一点。
 * ----------------------------------------------------------------
 */
static Oid	AuthenticatedUserId = InvalidOid;
static Oid	SessionUserId = InvalidOid;
static Oid	OuterUserId = InvalidOid;
static Oid	CurrentUserId = InvalidOid;

/* 我们还必须记住这些级别中的超级用户状态 */
static bool AuthenticatedUserIsSuperuser = false;
static bool SessionUserIsSuperuser = false;

static int	SecurityRestrictionContext = 0;

/* 我们还记得 SET ROLE 是否当前有效 */
static bool SetRoleIsActive = false;

/*
 * GetUserId - 获取当前有效用户 ID。
 *
 * 注意：不再有 SetUserId()；使用 SetUserIdAndSecContext()。
 */
Oid GetUserId(void)
{
	AssertState(OidIsValid(CurrentUserId));
	return CurrentUserId;
}


/*
 * GetOuterUserId/SetOuterUserId - 获取/设置外部级别用户 ID。
 */
Oid GetOuterUserId(void)
{
	AssertState(OidIsValid(OuterUserId));
	return OuterUserId;
}


static void fc_SetOuterUserId(Oid fc_userid, bool fc_is_superuser)
{
	AssertState(SecurityRestrictionContext == 0);
	AssertArg(OidIsValid(fc_userid));
	OuterUserId = fc_userid;

	/* 我们也强制有效用户 ID 匹配 */
	CurrentUserId = fc_userid;

	/* 还更新 is_superuser GUC 以匹配 OuterUserId 的属性 */
	SetConfigOption("is_superuser",
					fc_is_superuser ? "on" : "off",
					PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
}


/*
 * GetSessionUserId/SetSessionUserId - 获取/设置会话用户 ID。
 */
Oid GetSessionUserId(void)
{
	AssertState(OidIsValid(SessionUserId));
	return SessionUserId;
}

bool GetSessionUserIsSuperuser(void)
{
	Assert(OidIsValid(SessionUserId));
	return SessionUserIsSuperuser;
}

static void fc_SetSessionUserId(Oid fc_userid, bool fc_is_superuser)
{
	AssertState(SecurityRestrictionContext == 0);
	AssertArg(OidIsValid(fc_userid));
	SessionUserId = fc_userid;
	SessionUserIsSuperuser = fc_is_superuser;
}

/*
 * GetAuthenticatedUserId/SetAuthenticatedUserId - 获取/设置认证用户 ID。
 */
Oid GetAuthenticatedUserId(void)
{
	AssertState(OidIsValid(AuthenticatedUserId));
	return AuthenticatedUserId;
}

/*
 * 返回认证用户在连接开始时是否是超级用户。
 */
bool GetAuthenticatedUserIsSuperuser(void)
{
	Assert(OidIsValid(AuthenticatedUserId));
	return AuthenticatedUserIsSuperuser;
}

void SetAuthenticatedUserId(Oid fc_userid, bool fc_is_superuser)
{
	Assert(OidIsValid(fc_userid));

	/* 仅调用一次 */
	Assert(!OidIsValid(AuthenticatedUserId));

	AuthenticatedUserId = fc_userid;
	AuthenticatedUserIsSuperuser = fc_is_superuser;

	/* 还用认证用户 ID 标记我们的 PGPROC 条目 */
	/* （我们假设这是一个原子存储，因此不需要锁定） */
	MyProc->roleId = fc_userid;
}


/*
 * GetUserIdAndSecContext/SetUserIdAndSecContext - 获取/设置当前用户 ID
 * 和 SecurityRestrictionContext 标志。
 *
 * 当前 SecurityRestrictionContext 中有三个有效位：
 *
 * SECURITY_LOCAL_USERID_CHANGE 表示我们正在进行一个操作
 * ，该操作通过这些函数临时更改 CurrentUserId。这是
 * 需要的，以指示 CurrentUserId 的实际值与 guc.c 的内部状态不同步，
 * 因此必须禁止 SET ROLE。
 *
 * SECURITY_RESTRICTED_OPERATION 表示我们正在进行一个操作
 * ，该操作完全不希望信任被调用的用户定义函数。该
 * 策略是在操作之前使用这个标志，例如 autovacuum 和 REINDEX，这些操作
 * 枚举数据库或模式的关系并运行与每个找到的关系相关联的函数。
 * 关系所有者是新的用户 ID。尽快在锁定关系后设置这个标志。
 * 在关闭关系之前尽可能晚地恢复旧的用户 ID；在关闭之后不久恢复它也是可以接受的。
 * 如果一个命令有关系枚举模式和非枚举模式，例如 ANALYZE，这两种模式都会设置这个标志。
 * 这个标志不仅阻止 SET ROLE，还阻止各种其他通常不受保护的会话状态更改，
 * 但可能会在以后被用来颠覆调用会话。一个例子是用新代码替换现有的预编译语句，
 * 当下次使用预编译语句时它将以外部会话的权限执行。 这些限制相当严格，
 * 但是在关系枚举操作中调用的函数实际上应该是无副作用的。
 *
 * SECURITY_NOFORCE_RLS 表示我们正在进行一个操作，该操作应
 * 忽略每个表的 FORCE ROW LEVEL SECURITY 指示。用于
 * 确保 FORCE RLS 不会错误地破坏引用完整性检查。注意，
 * 这是故意仅在作为表的所有者运行时检查的（这对于
 * 引用完整性检查始终应为真）。
 *
 * 与 GetUserId 不同，GetUserIdAndSecContext *不* 断言当前
 * CurrentUserId 的值是有效的；而 SetUserIdAndSecContext 不要求
 * 新值是有效的。事实上，这些例程最好不要
 * 抛出任何错误。这是因为它们被 StartTransaction 和
 * AbortTransaction 用于保存/恢复设置，在后端的第一个事务中，
 * 要保存和可能恢复的值确实是无效的。我们必须能够在
 * AbortTransaction 过程中不进行断言，以防 InitPostgres 失败。
 */
void GetUserIdAndSecContext(Oid *fc_userid, int *fc_sec_context)
{
	*fc_userid = CurrentUserId;
	*fc_sec_context = SecurityRestrictionContext;
}

void SetUserIdAndSecContext(Oid fc_userid, int fc_sec_context)
{
	CurrentUserId = fc_userid;
	SecurityRestrictionContext = fc_sec_context;
}


/*
 * InLocalUserIdChange - 我们是否在 CurrentUserId 的本地更改中？
 */
bool InLocalUserIdChange(void)
{
	return (SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0;
}

/*
 * InSecurityRestrictedOperation - 我们是否在一个安全限制的命令中？
 */
bool InSecurityRestrictedOperation(void)
{
	return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0;
}

/*
 * InNoForceRLSOperation - 我们是否忽略了 FORCE ROW LEVEL SECURITY？
 */
bool InNoForceRLSOperation(void)
{
	return (SecurityRestrictionContext & SECURITY_NOFORCE_RLS) != 0;
}


/*
 * 这些是 Get/SetUserIdAndSecContext 的过时版本，仅用于与 pljava 中一些相当可疑的代码的错误兼容性。
 * 我们允许设置 userid，但仅在不处于安全限制上下文中时可以设置。
 */
void GetUserIdAndContext(Oid *fc_userid, bool *fc_sec_def_context)
{
	*fc_userid = CurrentUserId;
	*fc_sec_def_context = InLocalUserIdChange();
}

void SetUserIdAndContext(Oid fc_userid, bool fc_sec_def_context)
{
	/* 我们抛出的错误与 SET ROLE 一样。 */
	if (InSecurityRestrictedOperation())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("cannot set parameter \"%s\" within security-restricted operation",
						"role")));
	CurrentUserId = fc_userid;
	if (fc_sec_def_context)
		SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE;
	else
		SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE;
}


/*
 * 检查指定角色是否具有明确的 REPLICATION 权限
 */
bool has_rolreplication(Oid fc_roleid)
{
	bool		fc_result = false;
	HeapTuple	fc_utup;

	fc_utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
	if (HeapTupleIsValid(fc_utup))
	{
		fc_result = ((Form_pg_authid) GETSTRUCT(fc_utup))->rolreplication;
		ReleaseSysCache(fc_utup);
	}
	return fc_result;
}

/*
 * 在正常的后端启动期间初始化用户身份
 */
void InitializeSessionUserId(const char *fc_rolename, Oid fc_roleid)
{
	HeapTuple	fc_roleTup;
	Form_pg_authid fc_rform;
	char	   *fc_rname;
	bool		fc_is_superuser;

	/*
	 * 在并行工作线程中，我们不需要在这里做任何事情。
	 * ParallelWorkerMain 已经设置了我们的输出变量，我们不打算强制执行 rolcanlogin 或 rolconnlimit。
	 * 此外，我们不想为角色执行目录查找：如果角色已被删除，我们不想失败。
	 */
	if (InitializingParallelWorker)
		return;

	/*
	 * 如果我们正在引导，则不执行扫描，系统目录尚不存在，并且它们应该由 postgres 拥有。
	 */
	AssertState(!IsBootstrapProcessingMode());

	/*
	 * 确保最近的目录更改的 syscache 条目已被刷新。这使我们能够找到在身份验证期间动态创建的角色。
	 */
	AcceptInvalidationMessages();

	/*
	 * 查找角色，如果给定名称则按名称查找，否则按 OID 查找。
	 */
	if (fc_rolename != NULL)
	{
		fc_roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(fc_rolename));
		if (!HeapTupleIsValid(fc_roleTup))
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					 errmsg("role \"%s\" does not exist", fc_rolename)));
	}
	else
	{
		fc_roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
		if (!HeapTupleIsValid(fc_roleTup))
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					 errmsg("role with OID %u does not exist", fc_roleid)));
	}

	fc_rform = (Form_pg_authid) GETSTRUCT(fc_roleTup);
	fc_roleid = fc_rform->oid;
	fc_rname = NameStr(fc_rform->rolname);
	fc_is_superuser = fc_rform->rolsuper;

	SetAuthenticatedUserId(fc_roleid, fc_is_superuser);

	/*
	 * 通过 GUC 机制设置 SessionUserId 和相关变量，包括“角色”。
	 *
	 * 注意：理想情况下，我们在这里会使用 PGC_S_DYNAMIC_DEFAULT，以便随后可以从 pg_db_role_setting 条目更改 session_authorization。
	 * 但是，pg_db_role_setting 中的 session_authorization 没有影响。更改这一点将需要解决两个问题：
	 *
	 * 1. 如果 pg_db_role_setting 对 session_authorization 和角色都有值，我们无法确定它们的应用顺序，并且这很重要。
	 *
	 * 2. 站点可能有多年前的 session_authorization 条目。没有任何特定原因去删除它们。结束这些条目的休眠可能会严重改变应用程序行为，因此只有主要版本才应这样做。
	 */
	SetConfigOption("session_authorization", fc_rname,
					PGC_BACKEND, PGC_S_OVERRIDE);

	/*
	 * 当处于独立模式时，不强制执行以下检查，以便有办法从诸如“UPDATE pg_authid SET rolcanlogin = false;”这样的愚蠢操作中恢复。
	 */
	if (IsUnderPostmaster)
	{
		/*
		 * 角色是否允许登录？
		 */
		if (!fc_rform->rolcanlogin)
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					 errmsg("role \"%s\" is not permitted to log in",
							fc_rname)));

		/*
		 * 检查该角色的连接限制。我们仅针对常规后端强制执行该限制，因为其他进程类型有自己的 PGPROC 池。
		 *
		 * 此处存在竞争条件---我们在检查其他 PGPROC 之前创建了我们的 PGPROC。
		 * 如果两个后端在大约同一时间执行此操作，它们可能都认为超出了限制，而理想情况下一个应该成功，一个失败。
		 * 让这一点工作得如此精确似乎麻烦得不值得，然而；相反，我们只是记录连接限制是近似的。
		 */
		if (fc_rform->rolconnlimit >= 0 &&
			AmRegularBackendProcess() &&
			!fc_is_superuser &&
			CountUserBackends(fc_roleid) > fc_rform->rolconnlimit)
			ereport(FATAL,
					(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
					 errmsg("too many connections for role \"%s\"",
							fc_rname)));
	}

	ReleaseSysCache(fc_roleTup);
}


/*
 * 在特殊后端启动期间初始化用户身份
 */
void InitializeSessionUserIdStandalone(void)
{
	/*
	 * 此函数仅应在单用户模式、自动 vacuum 工作进程和后台工作进程中调用。
	 */
	AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker);

	/* 仅调用一次 */
	AssertState(!OidIsValid(AuthenticatedUserId));

	AuthenticatedUserId = BOOTSTRAP_SUPERUSERID;
	AuthenticatedUserIsSuperuser = true;

	/*
	 * XXX 理想情况下，我们会通过 SetConfigOption("session_authorization") 来实现，
	 * 但我们缺少执行此操作所需的角色名称，并且我们无法获取它，
	 * 因为这种特殊情况的一个原因是能够启动即使发生了
	 * 对 BOOTSTRAP_SUPERUSERID 的 pg_authid 行的影响。
	 * 由于我们没有设置 GUC，本身 C 代码将看到该值为 NULL，
	 * 并且 current_setting() 将在此会话内报告空字符串。
	 */
	SetSessionAuthorization(BOOTSTRAP_SUPERUSERID, true);

	/* 我们可以使用 SetConfigOption("role")，但让我们保持一致 */
	SetCurrentRoleId(InvalidOid, false);
}


/*
 * 在运行期间更改会话授权 ID
 *
 * SQL 标准指出 SET SESSION AUTHORIZATION 意味着 SET ROLE NONE。
 * 我们在更高层次上进行了机械化，而不是在这里，因为这是 "session_authorization" 的 GUC
 * 分配钩子，并且它必须与 SetCurrentRoleId（"role" 的钩子）是可交换的，
 * 因为 guc.c 不提供在事务回滚等情况下，哪个会先运行的保证。
 * 因此，只有当 !SetRoleIsActive 时，我们才更新派生状态（OuterUserId/CurrentUserId/is_superuser）。
 */
void SetSessionAuthorization(Oid fc_userid, bool fc_is_superuser)
{
	fc_SetSessionUserId(fc_userid, fc_is_superuser);

	if (!SetRoleIsActive)
		fc_SetOuterUserId(fc_userid, fc_is_superuser);
}

/*
 * 报告当前角色 ID
 *		这遵循 SET ROLE 的语义，即返回外层 ID
 *		而不是当前有效 ID，并在设置逻辑上为 SET ROLE NONE 时返回 InvalidOid。
 */
Oid GetCurrentRoleId(void)
{
	if (SetRoleIsActive)
		return OuterUserId;
	else
		return InvalidOid;
}

/*
 * 在运行期间更改角色 ID（SET ROLE）
 *
 * 如果 roleid 是 InvalidOid，我们正在执行 SET ROLE NONE：恢复为
 * 会话用户授权。在这种情况下，is_superuser 参数将被忽略。
 *
 * 当 roleid 不是 InvalidOid 时，调用者必须检查是否
 * 会话用户有权限成为该角色。 （我们无法在这里检查
 * 因为此例程必须能够在失败的事务中执行以恢复 ROLE GUC 变量的先前值。）
 */
void SetCurrentRoleId(Oid fc_roleid, bool fc_is_superuser)
{
	/*
	 * 如果是 SET ROLE NONE，则获取正确的信息
	 *
	 * 如果 SessionUserId 尚未设置，则只需更新
	 * SetRoleIsActive，其他操作不进行——最终的 SetSessionAuthorization 调用将
	 * 更新派生状态。这是必要的，因为我们将在 GUC 初始化期间被调用。
	 */
	if (!OidIsValid(fc_roleid))
	{
		SetRoleIsActive = false;

		if (!OidIsValid(SessionUserId))
			return;

		fc_roleid = SessionUserId;
		fc_is_superuser = SessionUserIsSuperuser;
	}
	else
		SetRoleIsActive = true;

	fc_SetOuterUserId(fc_roleid, fc_is_superuser);
}


/*
 * 从用户 oid 获取用户名，如果 noerr 为 true，则对不存在的 roleid 返回 NULL。
 */
char * GetUserNameFromId(Oid fc_roleid, bool fc_noerr)
{
	HeapTuple	fc_tuple;
	char	   *fc_result;

	fc_tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
	if (!HeapTupleIsValid(fc_tuple))
	{
		if (!fc_noerr)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("invalid role OID: %u", fc_roleid)));
		fc_result = NULL;
	}
	else
	{
		fc_result = pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(fc_tuple))->rolname));
		ReleaseSysCache(fc_tuple);
	}
	return fc_result;
}


/*-------------------------------------------------------------------------
 *				互锁文件支持
 *
 * 这些例程用于创建数据目录锁定文件
 * ($DATADIR/postmaster.pid) 和 Unix 套接字文件锁定文件 ($SOCKFILE.lock)。
 * 两种文件最初包含相同的信息，虽然我们可以在创建后
 * 使用 AddToDataDirLockFile() 向数据目录锁定文件添加
 * 更多信息。有关这些锁定文件内容的文档，请参见 pidfile.h。
 *
 * 在成功创建锁定文件后，将自动创建一个 proc_exit 回调来删除
 * 锁定文件。
 *-------------------------------------------------------------------------
 */

/*
 * proc_exit 回调以删除锁定文件。
 */
static void fc_UnlinkLockFiles(int fc_status, Datum fc_arg)
{
	ListCell   *fc_l;

	foreach(fc_l, lock_files)
	{
		char	   *fc_curfile = (char *) lfirst(fc_l);

		unlink(fc_curfile);
		/* 如果 unlink 失败我们应该抱怨吗？ */
	}
	/* 由于我们即将退出，因此无需回收存储 */
	lock_files = NIL;

	/*
	 * 锁定文件的删除应该始终是 postmaster 或独立后端的最后一个外部可见操作，
	 * 而在退出 postmaster 子进程时我们根本不会来到这里。因此，这是一个很好的
	 * 日志记录关机完成的地方。我们也可以选择教授
	 * proc_exit() 来执行此操作，但那似乎不太美观。在独立后端中，
	 * 使用 NOTICE 级别以减少喧哗。
	 */
	ereport(IsPostmasterEnvironment ? LOG : NOTICE,
			(errmsg("database system is shut down")));
#ifdef FDD
	if(NULL != fdb_write_audit_hook)
	{
		fdb_write_audit_hook("database system is shut down");
	}
#endif //FDD
}

/*
 * 创建锁定文件。
 *
 * filename 是要创建的锁定文件的路径名。
 * amPostmaster 用于确定如何编码输出 PID。
 * socketDir 是要包含的 Unix 套接字目录路径（可能为空）。
 * isDDLock 和 refName 用于确定生成什么错误消息。
 */
static void fc_CreateLockFile(const char *fc_filename, bool fc_amPostmaster,
			   const char *fc_socketDir,
			   bool fc_isDDLock, const char *fc_refName)
{
	int			fc_fd;
	char		fc_buffer[MAXPGPATH * 2 + 256];
	int			fc_ntries;
	int			fc_len;
	int			fc_encoded_pid;
	pid_t		fc_other_pid;
	pid_t		fc_my_pid,
				fc_my_p_pid,
				fc_my_gp_pid;
	const char *fc_envvar;

	/*
	 * 如果锁定文件中的 PID 是我们自己的 PID、我们的父 PID 或
	 * 祖父 PID，那么该文件必须是陈旧的（可能是上一系统启动周期残留的）。
	 * 我们需要检查这一点，因为重启后将分配与我们在
	 * 上一次重启中完全相同的 PID，或仅大一两的 PID，
	 * 因此锁定文件的 PID 现在指向一个祖先 shell 进程。
	 * 我们允许 pg_ctl 通过环境变量 PG_GRANDPARENT_PID 传递其父 shell PID
	 *（我们的祖父 PID）；这样，通过 pg_ctl 启动 postmaster
	 * 可以和直接启动一样可靠。没有检测
	 * 更远祖先进程的规定，但如果初始化脚本写得很小心，
	 * 则除直接父 shell 外的所有其他进程都将是 root 拥有的
	 * 进程，因此 kill 测试将因 EPERM 失败。请注意，我们
	 * 无法以这种方式得到假阴性，因为现有的 postmaster
	 * 绝不会直接启动一个竞争的 postmaster 或 pg_ctl 进程。
	 */
	fc_my_pid = getpid();

#ifndef WIN32
	fc_my_p_pid = getppid();
#else

	/*
	 * Windows 没有 getppid()，但由于它也不使用
	 * 真实的 kill()，因此不需要它...
	 */
	my_p_pid = 0;
#endif

	fc_envvar = getenv("PG_GRANDPARENT_PID");
	if (fc_envvar)
		fc_my_gp_pid = atoi(fc_envvar);
	else
		fc_my_gp_pid = 0;

	/*
	 * 我们在这里需要一个循环，因为会有竞态条件。但是不要循环太久
	 * （例如，一个不可写的 $PGDATA 目录可能导致一种无法消失的失败）。
	 * 100 次尝试似乎足够了。
	 */
	for (fc_ntries = 0;; fc_ntries++)
	{
		/*
		 * 尝试创建锁定文件 --- O_EXCL 使这成为原子操作。
		 *
		 * 请注意，不要将文件保护弱于 0600/0640。请参阅
		 * 下面的注释。
		 */
		fc_fd = open(fc_filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
		if (fc_fd >= 0)
			break;				/* 成功；退出重试循环 */

		/*
		 * 无法创建 pid 文件。可能它已经存在。
		 */
		if ((errno != EEXIST && errno != EACCES) || fc_ntries > 100)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not create lock file \"%s\": %m",
							fc_filename)));

		/*
		 * 读取文件以获取旧所有者的 PID。请注意竞争条件
		 * 在这里：自从我们尝试创建它以来，文件可能已经被删除。
		 */
		fc_fd = open(fc_filename, O_RDONLY, pg_file_create_mode);
		if (fc_fd < 0)
		{
			if (errno == ENOENT)
				continue;		/* 竞争条件；再试一次 */
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not open lock file \"%s\": %m",
							fc_filename)));
#ifdef FDD//cppcheck
			return;
#endif
		}
		pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_READ);
		if ((fc_len = read(fc_fd, fc_buffer, sizeof(fc_buffer) - 1)) < 0)
#ifdef FDD//cppcheck
		{
#endif
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not read lock file \"%s\": %m",
							fc_filename)));
#ifdef FDD//cppcheck
			return;
		}
#endif
		pgstat_report_wait_end();
		close(fc_fd);

		if (fc_len == 0)
		{
			ereport(FATAL,
					(errcode(ERRCODE_LOCK_FILE_EXISTS),
					 errmsg("lock file \"%s\" is empty", fc_filename),
					 errhint("Either another server is starting, or the lock file is the remnant of a previous server startup crash.")));
#ifdef FDD//cppcheck
			return;
#endif
		}

		fc_buffer[fc_len] = '\0';
		fc_encoded_pid = atoi(fc_buffer);

		/* 如果 pid < 0，则 pid 是指 postgres，而不是 postmaster */
		fc_other_pid = (pid_t) (fc_encoded_pid < 0 ? -fc_encoded_pid : fc_encoded_pid);

		if (fc_other_pid <= 0)
			elog(FATAL, "bogus data in lock file \"%s\": \"%s\"",
				 fc_filename, fc_buffer);

		/*
		 * 检查其他进程是否仍然存在
		 *
		 * 根据上述讨论，我的 my_pid、my_p_pid 和 my_gp_pid 可以
		 * 被忽略为错误匹配。
		 *
		 * 通常，kill() 如果给定的 PID 不存在，将失败并返回 ESRCH。
		 *
		 * 我们可以将 EPERM 错误情况视为可接受，因为该错误
		 * 意味着现有进程具有与我们不同的用户 ID，
		 * 这意味着它不能是竞争的 postmaster。postmaster
		 * 不能成功附加到由非其自己用户 ID 拥有的数据目录，
		 * 这一点在 checkDataDir() 中有强制规定。此外，由于我们
		 * 创建锁定文件的模式为 0600/0640，如果
		 * 锁定文件属于另一个用户 ID，我们将会在上面失败 ---
		 * 这意味着 kill() 报告的任何进程都不是创建锁定文件的
		 * 进程。（注意：这个最后的考虑是唯一防止我们删除属于
		 * 其他用户运行的 Postgres 实例的 Unix 套接字文件的原因，
		 * 至少在 /tmp 没有粘滞位的机器上。）
		 */
		if (fc_other_pid != fc_my_pid && fc_other_pid != fc_my_p_pid &&
			fc_other_pid != fc_my_gp_pid)
		{
			if (kill(fc_other_pid, 0) == 0 ||
				(errno != ESRCH && errno != EPERM))
			{
				/* 锁定文件属于一个活动进程 */
				ereport(FATAL,
						(errcode(ERRCODE_LOCK_FILE_EXISTS),
						 errmsg("lock file \"%s\" already exists",
								fc_filename),
						 fc_isDDLock ?
						 (fc_encoded_pid < 0 ?
						  errhint("Is another postgres (PID %d) running in data directory \"%s\"?",
								  (int) fc_other_pid, fc_refName) :
						  errhint("Is another postmaster (PID %d) running in data directory \"%s\"?",
								  (int) fc_other_pid, fc_refName)) :
						 (fc_encoded_pid < 0 ?
						  errhint("Is another postgres (PID %d) using socket file \"%s\"?",
								  (int) fc_other_pid, fc_refName) :
						  errhint("Is another postmaster (PID %d) using socket file \"%s\"?",
								  (int) fc_other_pid, fc_refName))));
			}
		}

		/*
		 * 不，创建进程不存在。然而，可能是
		 * postmaster 崩溃了（或者更可能是被一个无知的
		 * 管理员使用 kill -9 杀死）但留下了孤儿后端。
		 * 通过查看是否有一个仍在使用的相关 shmem 段来检查这一点。
		 *
		 * 注意：因为 postmaster.pid 是分多步写入的，我们可能
		 * 不能在其中找到 shmem ID 值；我们不能将其视为
		 * 错误。
		 */
		if (fc_isDDLock)
		{
			char	   *fc_ptr = fc_buffer;
			unsigned long fc_id1,
						fc_id2;
			int			fc_lineno;

			for (fc_lineno = 1; fc_lineno < LOCK_FILE_LINE_SHMEM_KEY; fc_lineno++)
			{
				if ((fc_ptr = strchr(fc_ptr, '\n')) == NULL)
					break;
				fc_ptr++;
			}

			if (fc_ptr != NULL &&
				sscanf(fc_ptr, "%lu %lu", &fc_id1, &fc_id2) == 2)
			{
				if (PGSharedMemoryIsInUse(fc_id1, fc_id2))
					ereport(FATAL,
							(errcode(ERRCODE_LOCK_FILE_EXISTS),
							 errmsg("pre-existing shared memory block (key %lu, ID %lu) is still in use",
									fc_id1, fc_id2),
							 errhint("Terminate any old server processes associated with data directory \"%s\".",
									 fc_refName)));
			}
		}

		/*
		 * 看起来没有人在家。取消链接文件并尝试再次创建
		 * 它。需要一个循环，因为可能会与其他
		 * 有意创建者发生竞争条件。
		 */
		if (unlink(fc_filename) < 0)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not remove old lock file \"%s\": %m",
							fc_filename),
					 errhint("The file seems accidentally left over, but "
							 "it could not be removed. Please remove the file "
							 "by hand and try again.")));
	}

	/*
	 * 成功创建了文件，现在填充它。请参阅 pidfile.h 中的注释
	 * 有关内容。注意，我们将相同的前五行写入
	 * datadir 和套接字锁定文件；尽管将来可能会向
	 * datadir 锁定文件添加更多内容。
	 */
	snprintf(fc_buffer, sizeof(fc_buffer), "%d\n%s\n" INT64_FORMAT "\n%d\n%s\n",
			 fc_amPostmaster ? (int) fc_my_pid : -((int) fc_my_pid),
			 DataDir,
			 MyStartTime,
			 PostPortNumber,
			 fc_socketDir);

	/*
	 * 在一个独立的后端中，下一行（LOCK_FILE_LINE_LISTEN_ADDR）
	 * 将永远不会接收数据，因此现在将其填充为空。
	 */
	if (fc_isDDLock && !fc_amPostmaster)
		strlcat(fc_buffer, "\n", sizeof(fc_buffer));

	errno = 0;
	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_WRITE);
	if (write(fc_fd, fc_buffer, strlen(fc_buffer)) != strlen(fc_buffer))
	{
		int			fc_save_errno = errno;

		close(fc_fd);
		unlink(fc_filename);
		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		errno = fc_save_errno ? fc_save_errno : ENOSPC;
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not write lock file \"%s\": %m", fc_filename)));
	}
	pgstat_report_wait_end();

	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_SYNC);
	if (pg_fsync(fc_fd) != 0)
	{
		int			fc_save_errno = errno;

		close(fc_fd);
		unlink(fc_filename);
		errno = fc_save_errno;
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not write lock file \"%s\": %m", fc_filename)));
	}
	pgstat_report_wait_end();
	if (close(fc_fd) != 0)
	{
		int			fc_save_errno = errno;

		unlink(fc_filename);
		errno = fc_save_errno;
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not write lock file \"%s\": %m", fc_filename)));
	}

	/*
	 * 安排在 proc_exit 时取消锁定文件。如果这是第一个
	 * 文件，设置 on_proc_exit 函数来执行此操作；然后将此锁定文件
	 * 添加到要取消链接的文件列表中。
	 */
	if (lock_files == NIL)
		on_proc_exit(fc_UnlinkLockFiles, 0);

	/*
	 * 使用 lcons，以便锁定文件按创建的逆序取消链接；这非常关键！
	 */
	lock_files = lcons(pstrdup(fc_filename), lock_files);
}

/*
 * 创建数据目录锁定文件。
 *
 * 在调用此函数时，我们必须已经将工作目录切换到 DataDir，因此我们可以直接使用相对路径。这
 * 有助于确保我们锁定的是应该锁定的目录。
 *
 * 注意，套接字目录路径行最初是作为空白写入的。在创建第一个 Unix 套接字时，postmaster.c 会重写
 * 它。
 */
void CreateDataDirLockFile(bool fc_amPostmaster)
{
	fc_CreateLockFile(DIRECTORY_LOCK_FILE, fc_amPostmaster, "", true, DataDir);
}

/*
 * 为指定的 Unix 套接字文件创建一个锁定文件。
 */
void CreateSocketLockFile(const char *fc_socketfile, bool fc_amPostmaster,
					 const char *fc_socketDir)
{
	char		fc_lockfile[MAXPGPATH];

	snprintf(fc_lockfile, sizeof(fc_lockfile), "%s.lock", fc_socketfile);
	fc_CreateLockFile(fc_lockfile, fc_amPostmaster, fc_socketDir, false, fc_socketfile);
}

/*
 * TouchSocketLockFiles -- 标记套接字锁定文件为最近访问
 *
 * 此例程应定期调用，以确保套接字锁定文件具有最近的修改或访问日期。这样可以防止它们
 * 被过于热情的 /tmp 目录清理守护进程删除。（我们永远不应该将套接字文件放入 /tmp 的另一个原因…）
 */
void TouchSocketLockFiles(void)
{
	ListCell   *fc_l;

	foreach(fc_l, lock_files)
	{
		char	   *fc_socketLockFile = (char *) lfirst(fc_l);

		/* 无需触摸数据目录锁定文件，我们信任 */
		if (strcmp(fc_socketLockFile, DIRECTORY_LOCK_FILE) == 0)
			continue;

		/* 我们只是忽略此处的任何错误 */
		(void) utime(fc_socketLockFile, NULL);
	}
}


/*
 * 在数据目录锁定文件中添加（或替换）一行。
 * 给定的字符串不应包括尾随换行符。
 *
 * 注意：由于我们不截断文件，如果我们用比之前少的数据重写一行，则最后一行后面会有垃圾。
 * 虽然我们可以通过添加一个截断调用来解决这个问题，但这会使文件更新变得非原子化，我们更希望避免这样。
 * 因此，调用者应该力求在写入后不缩短行。
 */
void AddToDataDirLockFile(int fc_target_line, const char *fc_str)
{
	int			fc_fd;
	int			fc_len;
	int			fc_lineno;
	char	   *fc_srcptr;
	char	   *fc_destptr;
	char		fc_srcbuffer[BLCKSZ];
	char		fc_destbuffer[BLCKSZ];

	fc_fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0);
	if (fc_fd < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
		return;
	}
	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ);
	fc_len = read(fc_fd, fc_srcbuffer, sizeof(fc_srcbuffer) - 1);
	pgstat_report_wait_end();
	if (fc_len < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not read from file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
		close(fc_fd);
		return;
	}
	fc_srcbuffer[fc_len] = '\0';

	/*
	 * 跳过不应重写的行，然后将它们复制到 destbuffer。
	 */
	fc_srcptr = fc_srcbuffer;
	for (fc_lineno = 1; fc_lineno < fc_target_line; fc_lineno++)
	{
		char	   *fc_eol = strchr(fc_srcptr, '\n');

		if (fc_eol == NULL)
			break;				/* 文件中尚未有足够的行 */
		fc_srcptr = fc_eol + 1;
	}
	memcpy(fc_destbuffer, fc_srcbuffer, fc_srcptr - fc_srcbuffer);
	fc_destptr = fc_destbuffer + (fc_srcptr - fc_srcbuffer);

	/*
	 * 在目标行之前填充任何缺失的行，以防文件中按错误顺序添加行。
	 */
	for (; fc_lineno < fc_target_line; fc_lineno++)
	{
		if (fc_destptr < fc_destbuffer + sizeof(fc_destbuffer))
			*fc_destptr++ = '\n';
	}

	/*
	 * 写入或重写目标行。
	 */
	snprintf(fc_destptr, fc_destbuffer + sizeof(fc_destbuffer) - fc_destptr, "%s\n", fc_str);
	fc_destptr += strlen(fc_destptr);

	/*
	 * 如果旧文件中还有更多行，则将它们追加到 destbuffer。
	 */
	if ((fc_srcptr = strchr(fc_srcptr, '\n')) != NULL)
	{
		fc_srcptr++;
		snprintf(fc_destptr, fc_destbuffer + sizeof(fc_destbuffer) - fc_destptr, "%s",
				 fc_srcptr);
	}

	/*
	 * 并重写数据。由于我们在单个内核调用中写入，因此该更新对于旁观者应看作是原子的。
	 */
	fc_len = strlen(fc_destbuffer);
	errno = 0;
	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE);
	if (pg_pwrite(fc_fd, fc_destbuffer, fc_len, 0) != fc_len)
	{
		pgstat_report_wait_end();
		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		if (errno == 0)
			errno = ENOSPC;
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
		close(fc_fd);
		return;
	}
	pgstat_report_wait_end();
	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC);
	if (pg_fsync(fc_fd) != 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
	}
	pgstat_report_wait_end();
	if (close(fc_fd) != 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
	}
}


/*
 * 重新检查数据目录锁定文件是否仍然存在并具有预期内容。
 * 如果锁定文件看起来正常，则返回 true；如果不是，则返回 false。
 *
 * 我们在 postmaster 中定期调用此函数。其想法是，如果锁定文件被删除或被另一个 postmaster 替换，
 * 我们应该执行紧急数据库关闭。因此，如果有任何疑问，我们应该返回 true：我们不希望不必要地
 * 造成紧急关闭。像 EINTR 或 ENFILE 这样的瞬态故障不应导致我们失败。
 * （如果真的有问题，我们将在未来的重新检查中检测到它。）
 */
bool RecheckDataDirLockFile(void)
{
	int			fc_fd;
	int			fc_len;
	long		fc_file_pid;
	char		fc_buffer[BLCKSZ];

	fc_fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0);
	if (fc_fd < 0)
	{
		/*
		 * 有许多可预见的误报错误条件。为了安全，仅在列举的明显有问题的条件下失败。
		 */
		switch (errno)
		{
			case ENOENT:
			case ENOTDIR:
				/* 灾难 */
				ereport(LOG,
						(errcode_for_file_access(),
						 errmsg("could not open file \"%s\": %m",
								DIRECTORY_LOCK_FILE)));
				return false;
			default:
				/* 非致命，至少目前是这样 */
				ereport(LOG,
						(errcode_for_file_access(),
						 errmsg("could not open file \"%s\": %m; continuing anyway",
								DIRECTORY_LOCK_FILE)));
				return true;
		}
	}
	pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_RECHECKDATADIR_READ);
	fc_len = read(fc_fd, fc_buffer, sizeof(fc_buffer) - 1);
	pgstat_report_wait_end();
	if (fc_len < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not read from file \"%s\": %m",
						DIRECTORY_LOCK_FILE)));
		close(fc_fd);
		return true;			/* 将读取失败视为非致命 */
	}
	fc_buffer[fc_len] = '\0';
	close(fc_fd);
	fc_file_pid = atol(fc_buffer);
	if (fc_file_pid == getpid())
		return true;			/* 一切正常 */

	/* 问题：某人覆盖了锁定文件 */
	ereport(LOG,
			(errmsg("lock file \"%s\" contains wrong PID: %ld instead of %ld",
					DIRECTORY_LOCK_FILE, fc_file_pid, (long) getpid())));
	return false;
}


/*-------------------------------------------------------------------------
 *				版本检查支持
 *-------------------------------------------------------------------------
 */

/*
 * 确定目录`path`中的PG_VERSION文件是否指示
 * 与此程序版本兼容的数据版本。
 *
 * 如果兼容，则返回。否则，ereport(FATAL)。
 */
void ValidatePgVersion(const char *fc_path)
{
	char		fc_full_path[MAXPGPATH];
	FILE	   *fc_file;
	int			fc_ret;
	long		fc_file_major;
	long		fc_my_major;
	char	   *fc_endptr;
	char		fc_file_version_string[64];
	const char *fc_my_version_string = PG_VERSION;

	fc_my_major = strtol(fc_my_version_string, &fc_endptr, 10);

	snprintf(fc_full_path, sizeof(fc_full_path), "%s/PG_VERSION", fc_path);

	fc_file = AllocateFile(fc_full_path, "r");
	if (!fc_file)
	{
		if (errno == ENOENT)
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("\"%s\" is not a valid data directory",
							fc_path),
					 errdetail("File \"%s\" is missing.", fc_full_path)));
		else
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\": %m", fc_full_path)));
	}

	fc_file_version_string[0] = '\0';
	fc_ret = fscanf(fc_file, "%63s", fc_file_version_string);
	fc_file_major = strtol(fc_file_version_string, &fc_endptr, 10);

	if (fc_ret != 1 || fc_endptr == fc_file_version_string)
		ereport(FATAL,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" is not a valid data directory",
						fc_path),
				 errdetail("File \"%s\" does not contain valid data.",
						   fc_full_path),
				 errhint("You might need to initdb.")));

	FreeFile(fc_file);

	if (fc_my_major != fc_file_major)
		ereport(FATAL,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("database files are incompatible with server"),
				 errdetail("The data directory was initialized by PostgreSQL version %s, "
						   "which is not compatible with this version %s.",
						   fc_file_version_string, fc_my_version_string)));
}

/*-------------------------------------------------------------------------
 *				库预加载支持
 *-------------------------------------------------------------------------
 */

/*
 * GUC变量：在postmaster启动和后端启动时预加载的库名称列表
 */
char	   *session_preload_libraries_string = NULL;
char	   *shared_preload_libraries_string = NULL;
char	   *local_preload_libraries_string = NULL;

/* 标志，表示我们正在加载shared_preload_libraries */
bool		process_shared_preload_libraries_in_progress = false;
bool		process_shared_preload_libraries_done = false;

shmem_request_hook_type shmem_request_hook = NULL;
bool		process_shmem_requests_in_progress = false;

/*
 * 加载'libraries'中列出的共享库
 *
 * 'gucname'：GUC变量的名称，用于错误报告
 * 'restricted'：如果为真，则强制库位于$libdir/plugins/
 */
static void fc_load_libraries(const char *fc_libraries, const char *fc_gucname, bool fc_restricted)
{
	char	   *fc_rawstring;
	List	   *fc_elemlist;
	ListCell   *fc_l;

	if (fc_libraries == NULL || fc_libraries[0] == '\0')
		return;					/* 无需操作 */

	/* 需要可修改的字符串副本 */
	fc_rawstring = pstrdup(fc_libraries);

	/* 将字符串解析为文件路径列表 */
	if (!SplitDirectoriesString(fc_rawstring, ',', &fc_elemlist))
	{
		/* 列表中的语法错误 */
		list_free_deep(fc_elemlist);
		pfree(fc_rawstring);
		ereport(LOG,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("invalid list syntax in parameter \"%s\"",
						fc_gucname)));
		return;
	}

	foreach(fc_l, fc_elemlist)
	{
		/* 注意，文件名已被规范化 */
		char	   *fc_filename = (char *) lfirst(fc_l);
		char	   *fc_expanded = NULL;

		/* 如果限制，则在未提及的情况下插入$libdir/plugins */
		if (fc_restricted && first_dir_separator(fc_filename) == NULL)
		{
			fc_expanded = psprintf("$libdir/plugins/%s", fc_filename);
			fc_filename = fc_expanded;
		}
		load_file(fc_filename, fc_restricted);
		ereport(DEBUG1,
				(errmsg_internal("loaded library \"%s\"", fc_filename)));
		if (fc_expanded)
			pfree(fc_expanded);
	}

	list_free_deep(fc_elemlist);
	pfree(fc_rawstring);
}

/*
 * 处理应在postmaster启动时预加载的任何库
 */
void process_shared_preload_libraries(void)
{
	process_shared_preload_libraries_in_progress = true;
	fc_load_libraries(shared_preload_libraries_string,
				   "shared_preload_libraries",
				   false);
	process_shared_preload_libraries_in_progress = false;
	process_shared_preload_libraries_done = true;
}

/*
 * 处理应在后端启动时预加载的任何库
 */
void process_session_preload_libraries(void)
{
	fc_load_libraries(session_preload_libraries_string,
				   "session_preload_libraries",
				   false);
	fc_load_libraries(local_preload_libraries_string,
				   "local_preload_libraries",
				   true);
}

/*
 * 处理来自预加载库的任何共享内存请求
 */
void process_shmem_requests(void)
{
	process_shmem_requests_in_progress = true;
	if (shmem_request_hook)
		shmem_request_hook();
	process_shmem_requests_in_progress = false;
}

void pg_bindtextdomain(const char *fc_domain)
{
#ifdef ENABLE_NLS
	if (my_exec_path[0] != '\0')
	{
		char		fc_locale_path[MAXPGPATH];

		get_locale_path(my_exec_path, fc_locale_path);
		bindtextdomain(fc_domain, fc_locale_path);
		pg_bind_textdomain_codeset(fc_domain);
	}
#endif
}
